﻿/*<body><script type="text/javascript">
var Print = (function() {
  if (typeof(document)!='undefined') {
    document.body.innerHTML = '';
    return function() {
      document.getElementById('output').innerHTML +=
        argJoin(Array.prototype.flatten.call(arguments), '<br />');
    };
  } else {
    return function() {
      print(argJoin(Array.prototype.flatten.call(arguments), '\n'));
    };
  }
})();
Array.prototype.isArray = true;
Array.prototype.flatten = function(includeUndefined) {
  var result = [];
  var len = this.length;
  for (var i = 0; i < len; i++) {
    var el = this[i];
    if (typeof(el)!='undefined' && el.isArray) {
      result = result.concat(el.flatten(includeUndefined));
    } else if (includeUndefined || el !== undefined) {
      result.push(el);
    }
  }
  return result;
};

escapeStringFor = new Object();
for (var c = 0; c < 256; c++) {
  escapeStringFor[c] = String.fromCharCode(c) }
escapeStringFor["\\".charCodeAt(0)] = "\\\\"
escapeStringFor['"'.charCodeAt(0)]  = '\\"'
escapeStringFor["'".charCodeAt(0)]  = "\\'"
escapeStringFor["\r".charCodeAt(0)] = "\\r"
escapeStringFor["\n".charCodeAt(0)] = "\\n"
escapeStringFor["\t".charCodeAt(0)] = "\\t"
escapeStringFor["\f".charCodeAt(0)] = "\\f"
escapeChar = function(c) {
  return c > 255 ? String.fromCharCode(c) : escapeStringFor[c]
}

function unescapeString(s) {
  if (s[0] == '\\')
    switch (s[1]) {
      case '\\': return '\\'
      case 'r':  return '\r'
      case 'n':  return '\n'
      case 't':  return '\t'
      case "'":  return "'"
      case '"':  return '"'
      default:   return s[1]
    }
  else
    return s
}

String.prototype.toProgramString = function() {
  var ws = ["\""], c;
  for (var idx = 0, len = this.length; idx < len; idx++) {
    ws[idx+1] = ((c = this.charCodeAt(idx)) > 255 ? String.fromCharCode(c) : escapeStringFor[c]);
  }
  ws[idx+1] = "\"";
  return new String(ws.join(''));
}

function argToString(obj) {
  if (typeof(obj)=='string') return obj.toProgramString();
  return obj.toString ? obj.toString() : 'undefined';
}
function argJoin(obj, joiner) {
  var ws = new Array(obj.length);
  for (var idx = 0, len = obj.length; idx < len; idx++) {
    ws[idx] = argToString(obj[idx]);
  }
  return ws.join(typeof(joiner)!='undefined' ? joiner : ', ');
}
</script></body><div id="output" style='font-size:36;font-family:Consolas'></div><div style='display:none'><script src='ojs-stripped.html'></script>*/
var Func = (function () {
  
  var fNameExtract = /^[^(]+\s+([^(]+)/;
  
  // Activation-functor generator/installer. 
  //   Installs a function in "targetContext"
  //   that (when invoked) saves an activation
  //   of itself, most importantly including
  //   its stringification.
  function _Func(targetContext, func) {
    
    var self, fName = 
      func.toString().match(fNameExtract)[1];
    
    return targetContext[fName] = self = 
      function () {
        return new Act(fName, func, arguments);
    };
  };
  
  // Activation frame constructor. Called with
  //   the "new " operator, a JavaScript
  //   function's "this" keyword refers to an
  //   object that initially inherits all its
  //   properties from the .prototype property.
  //   "this"'s .constructor property is then
  //   a reference to the constructor function.
  function Act(name, func, args, str, invoker) {
    this.func = func; // execution "method"
    this.args = args; // closed-over arg array
    this.invoker = invoker; // who invoked
    this.str = str || name+'('+argJoin(args)+')';
  }
  
  // Prototype object (default properties and
  //   instance methods) of Activation frames.
  Act.prototype = {
    done: true // whether the call is exhausted
  , outcome: true // whether the call succeeded
  , advanced: 0 // how many positions advanced
  , phase: 0 // an NFA's state marker
  , toString: function () {
      return this.str; // cached stringification
    }
  , run: function (localContext) {
      var act = this, last = this, Last = this;
      Last.localContext = localContext;
      while (act) { // continuation-returning-style
        Last = last;
        act = (last = act).func(Last.localContext);
      } // aka "trampolining"
      return this.result;
    }
  , reactivate: function (invoker) {
      return new Act(null, this.func,
        this.args, this.str, invoker);
    }
  };
  
  return _Func;
})();

var context = { index: 0 };

Func(context, function both(localContext) {
  var first = this.first, second = this.second;
  switch (this.phase) {
    
    case 0:
    this.localContext = localContext;
    this.index = this.localContext.index;
    first = this.first = this.args[0].reactivate(this);
    second = this.second = this.args[1].reactivate(this);
    this.phase = 1;
    return first;
    
    case 1:
    if (first.outcome) {
      this.phase = 2;
      return second;
    } else {
      this.outcome = false;
      this.localContext.index = this.index;
      this.advanced = 0;
      this.done = true;
      return this.invoker;
    }
    
    case 2: if (second.outcome) {
      this.advanced = first.advanced + second.advanced;
      if (!second.done) {
        this.done = false;
        this.phase = 4;
      } else if (!first.done) {
        this.done = false;
        this.phase = 3;
      } else {
        this.done = true;
      }
      this.result = new Array(first.result, second.result);
      return this.invoker;
    } else if (first.done) {
      this.outcome = false;
      this.advanced = 0;
      this.done = true;
      return this.invoker;
    }
    // intentionally fall through to phase 3
    
    case 3:
    this.localContext.index = this.index;
    this.phase = 1;
    second = this.second = this.args[1].reactivate(this);
    return first;
    
    case 4:
    this.localContext.index = second.index;
    this.phase = 2;
    return second;
  }
});

Func(context, function either(localContext) {
  var first = this.first, second = this.second;
  switch (this.phase) {
  
    case 0:
    this.localContext = localContext;
    this.index = this.localContext.index;
    this.first = this.args[0].reactivate(this);
    this.second = this.args[1].reactivate(this);
    this.phase = 1;
    return this.first;
    
    case 1: if (first.outcome) {
      this.done = false;
      this.phase = first.done ? 2 : 3;
      this.result = first.result;
      this.advanced = first.advanced;
      return this.invoker;
    }
    // intentionally fall through to case 2.
    
    case 2:
    this.localContext.index = this.index;
    this.phase = 4;
    return second;
    
    case 3:
    this.localContext.index = this.index;
    this.phase = 1;
    return first;
    
    case 4: if (second.outcome) {
      this.done = second.done;
      this.phase = 2;
      this.result = second.result;
      this.vars = second.vars;
      this.label = second.label;
      this.advanced = second.advanced;
      return this.invoker;
    } else {
      this.outcome = false;
      this.localContext.index = this.index;
      this.advanced = 0;
      this.done = true;
      return this.invoker;
    }
  }
});


Func(context, function literal(localContext) {
  this.phase++;
  var n = localContext.input
  , x = this.index = localContext.index;
  
  if (typeof(this.outcome = n.charAt(x + this.args[0].length - 1))=='undefined') {
    this.outcome = false;
    return this.invoker;
  }
  if ((this.result = n.substring(x, x + this.args[0].length))==this.args[0]) {
    localContext.index += (this.advanced = this.args[0].length);
    return this.invoker;
  }
  this.outcome = false;
  return this.invoker;
});

Func(context, function any(localContext) {
  var n = localContext.input
  , x = this.index = localContext.index;
  
  if (typeof(this.outcome = n.charAt(x))=='undefined') {
    this.outcome = false;
    return this.invoker;
  }
  this.result = n.charAt(x);
  localContext.index += (this.advanced = 1);
  return this.invoker;
});

var parser = (function() { with (context) {
  return both(
    both(
      literal('hi'), literal('hi')
    ), literal('hi'));
}})();

context.input = "hihihi";
context.len = context.input.length;

Print(context.input, parser, parser.run(context));